package pm.oo.util;

import com.sun.star.frame.XFrame;
import com.sun.star.frame.XFramesSupplier;
import com.sun.star.frame.XFrames;
import com.sun.star.uno.XComponentContext;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.awt.XWindow;
import com.sun.star.awt.XToolkit;
import com.sun.star.awt.XSystemChildFactory;
import com.sun.star.awt.XWindowPeer;
import com.sun.star.lang.XComponent;
import pm.oo.NativeOOViewComponent;
import pm.oo.util.OfficeConnection;

/**
 * @author Anton Bernatski
 * @version $Revision$ $Date$
 */
public class FramesCreator {
	/**
	 * create a new empty target frame
	 * Attention: Currently we must use special service com.sun.star.frame.Task instead of Frame.
	 * Because desktop environment accept this special frame type only as direct children.
	 * Note - This service will be deprecated and must be replaces by com.sun.star.frame.Frame in
	 * further versions. To feature prove we use both service names. If for new versions
	 * the deprecated one not exist we get an empty frame, we can try to use the new service.
	 *
	 * @param xCtx we nee the remote service manager to create this task/frame service
	 * @return [com.sun.star.frame.XFrame]
	 *         the new created frame reference in case of success or null otherwhise
	 * @throws com.sun.star.uno.Exception todo
	 */
	private static XFrame createEmptyFrame(XComponentContext xCtx) throws com.sun.star.uno.Exception {
		Object frameObj = xCtx.getServiceManager().createInstanceWithContext("com.sun.star.frame.Frame", xCtx);
		return (XFrame) UnoRuntime.queryInterface(XFrame.class, frameObj);
	}

	/**
	 * create a new window which can be used as container window of an office frame
	 * We know two modes for creation:
	 * - the office window will be a child of one of our java windows
	 * - the office will be a normal system window outside this java application
	 * This behaviour will be regulated by the second parameter of this operation.
	 * If a parentview is given the first mode will be activated - otherwhise
	 * the second one.
	 * <p/>
	 * Note: First mode (creation of a child window) can be reached by two different
	 * ways.
	 * - pack the required window handle of our java window inside an UNO object
	 * to transport it to the remote office toolkit and get a child office
	 * window.
	 * This is the old way. It's better to use the second one - but to be
	 * future prove this old one should be tried too.
	 * - it's possible to pass the native window handle directly to the toolkit.
	 * A special interface method was enabled to accept that.
	 * <p/>
	 * The right way to create an office window should be then:
	 * - try to use second creation mode (directly using of the window handle)
	 * - if it failed ... use the old way by packing the handle inside an object
	 *
	 * @param xCtx		we need a service manager to be able to create remote office
	 *                    services
	 * @param aParentView the java window as parent for the office window if an inplace office
	 *                    is required. If it is set to null the created office window will be
	 *                    a normal system window outside of our java application.
	 * @return [com.sun.star.awt.XWindow]
	 *         The new created office window which can be used to set it as
	 *         a ContainerWindow on an empty office frame.
	 */
	private static XWindow createWindow(XComponentContext xCtx, NativeOOViewComponent aParentView)
			throws com.sun.star.uno.Exception {

		if (aParentView == null) {
			throw new NullPointerException();
		}

		// get access to toolkit of remote office to create the container window of
		// new target frame
		Object toolkitObj = xCtx.getServiceManager().createInstanceWithContext("com.sun.star.awt.Toolkit", xCtx);
		XToolkit xToolkit = (XToolkit) UnoRuntime.queryInterface(XToolkit.class, toolkitObj);

		/*
				mode 2) create an internal office window as child of our given java
				parent window
				try new version of creation first: directly using of the window
				handle. The old implementation of the corresponding toolkit method
				requires a process ID. If this id isn't the right one a null object
				is returned. But normaly nobody outside the office knows this id.
				New version of this method ignore the id parameter and creation will
				work.
				Note: You must be shure if your window handle can be realy used by
				the remote office. Means if this java client and the remote office
				use the same display!
				*/

		XSystemChildFactory xChildFactory = (XSystemChildFactory) UnoRuntime.queryInterface(XSystemChildFactory.class, xToolkit);

		XWindowPeer xPeer;
		try {
			Integer nHandle = aParentView.getHWND();
			short nSystem = (short) aParentView.getNativeWindowSystemType();
			byte[] lProcID = new byte[0];

			xPeer = xChildFactory.createSystemChild(nHandle, lProcID, nSystem);

		}
		catch (com.sun.star.uno.RuntimeException exJRun) {
			/*
						 This exception is thrown by the native JNI code if it try to get
						 the systemw window handle. A possible reason can be an invisible
						 java window. In this case it should be enough to set return
						 values to null. All other ressources (which was created before)
						 will be freed automaticly if scope wil be leaved.
						*/
			System.out.println("May be the NativeView object wasn't realy visible at calling time of getNativeWindow()?");
			xPeer = null;
		}

		// It doesn't matter which way was used to get the window peer.
		// Cast it to the right return interface and return it.
		XWindow xWindow = (XWindow) UnoRuntime.queryInterface(XWindow.class, xPeer);
		return xWindow;
	}

	/**
	 * This method create a new empty child frame on desktop instance of remote office.
	 * It use a special JNI functionality to pass the office XWindow over a java window.
	 * This java window can be inserted into another java window container for complex layouting.
	 * If this parent java window isn't used, a top level system window will be created.
	 * The the resulting office frame isn't plugged into this java application.
	 *
	 * @param sName	   name to set it on the new created frame
	 * @param aParentView java window which should be used as parent window of new created office frame window
	 *                    May be set to null.
	 * @return [com.sun.star.frame.XFrame]
	 *         reference to the new created frame for success or null if it failed
	 */
	public static XFrame createViewFrame(String sName, NativeOOViewComponent aParentView) {
		XFrame xFrame = null;

		try {
			XComponentContext xCtx = OfficeConnection.getOfficeContext();

			xFrame = createEmptyFrame(xCtx);

			XWindow xWindow = createWindow(xCtx, aParentView);

			xFrame.initialize(xWindow);

			// Insert the new frame in desktop hierarchy.
			// Use XFrames interface to do so. It provides access to the child frame container of that instance.
			Object desktopObj = xCtx.getServiceManager().createInstanceWithContext("com.sun.star.frame.Desktop", xCtx);
			XFramesSupplier xTreeRoot = (XFramesSupplier) UnoRuntime.queryInterface(XFramesSupplier.class, desktopObj);
			XFrames xChildContainer = xTreeRoot.getFrames();
			xChildContainer.append(xFrame);

			// Make some further initializations on frame and window.
			xWindow.setVisible(true);
			xFrame.setName(sName);
		}
		catch (com.sun.star.uno.RuntimeException exRuntime) {
			// Any UNO method of this scope can throw this exception.
			// So the frame can be already created and it must be freed
			// correctly. May be it was inserted into the desktop tree too ...
			if (xFrame != null) {
				// Try to dispose the frame. It should deregister himself at the desktop object
				// and free all internal used ressources (e.g. the container window) automaticly.
				// It's possible to do that here - because frame has no component inside yet.
				// So nobody can disagree with that.
				// After the dispose() call forget all references to this frame and let it die.
				// If a new exception will occure ... no generell solution exist then.
				// Nobody can guarantee if nextPage call will work or not.
				XComponent xComponent = (XComponent) UnoRuntime.queryInterface(XComponent.class, xFrame);
				xComponent.dispose();
				xFrame = null;
				exRuntime.printStackTrace();
			}
		}
		catch (com.sun.star.uno.Exception exUno) {
			// "createInstance()" method of used service manager can throw it.
			// If it occured during creation of desktop service the frame already was created.
			// Free it by decresing his refcount. Changes on the desktop tree couldn't exist.
			// Without the desktop service that wasn't possible. So no further rollbacks must follow.
			if (xFrame != null) {
				XComponent xComponent = (XComponent) UnoRuntime.queryInterface(XComponent.class, xFrame);
				xComponent.dispose();
				xFrame = null;
				exUno.printStackTrace();
			}
		}

		return xFrame;
	}
}
